package cn.leaf.calculator;

import cn.hutool.core.util.ReflectUtil;
import cn.leaf.calculator.bean.CalResultEntity;
import cn.leaf.calculator.bean.CalUnitBeanDefinition;
import cn.leaf.calculator.bean.ParameterEntity;
import cn.leaf.calculator.convert.AnnotationFormulaConvert;
import cn.leaf.calculator.convert.AnnotationFormulaDefinitionConvert;
import cn.leaf.calculator.datasource.AbstractCalUnitDataSource;
import cn.leaf.calculator.datasource.CalUnitDataSource;
import cn.leaf.calculator.engine.FormulaCalEngine;
import cn.leaf.calculator.engine.SpELFormulaCalEngine;

import lombok.extern.slf4j.Slf4j;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;

/**
 * 注解的公式计算器
 *
 * @author 李克国
 * @version 1.0.0
 */
@Slf4j
public class AnnotationFormulaCalculator<T> extends AbstractFormulaCalculator<T> {

    private final Class<T> definition;
    /**
     * 转换器
     */
    private AnnotationFormulaConvert<T> convert;

    private FormulaCalEngine<T> formulaCalEngine;

    public AnnotationFormulaCalculator(CalUnitDataSource<T> dataSources, Class<T> definition) {
        setDataSource(dataSources);
        this.definition = definition;
        initialize();
    }

    public AnnotationFormulaCalculator(Function<Collection<Serializable>, Collection<T>> executor, Collection<T> values, Class<T> definition) {
        dataSources = new AbstractCalUnitDataSource<T>(values) {
            @Override
            public Collection<T> bindUpdateParameters(Collection<Serializable> parameterIds) {
                return executor.apply(parameterIds);
            }
        };
        this.definition = definition;
        initialize();
    }


    @Override
    public boolean cal(CalUnitBeanDefinition<T> calUnitBean) {
        Collection<Field> calItemFields = convert.getCalItemFields();
        if (calUnitBean.getFormulaString() == null) {
            return true;
        }
        Map<Serializable, CalResultEntity> resultEntityMap = new HashMap<>(calItemFields.size());
        for (Field field : calItemFields) {
            // 计算
            CalResultEntity result = formulaCalEngine.execute(calUnitBean.getFormulaString(), createParams(calUnitBean, field));
            result.setFormulaName(calUnitBean.getFormulaName());
            result.setPropertyName(field.getName());
            log.info("id:{}, property:{}, formula:{}, expression:{}, result:{}, message:{}",
                    calUnitBean.getId(), field.getName(), result.getFormula(), result.getCalExpression(), result.getResult(), result.getMessage());
            // 设置值
            ReflectUtil.setFieldValue(calUnitBean.getValue(), field, result.getResult());
            resultEntityMap.put(field.getName(), result);
        }
        tryToSetResultMapByAppoint(calUnitBean, resultEntityMap);

        calUnitBean.setCalResultMap(resultEntityMap);
        return true;
    }

    private void tryToSetResultMapByAppoint(CalUnitBeanDefinition<T> calUnitBean, Map<Serializable, CalResultEntity> resultEntityMap) {
        if (ReflectUtil.hasField(convert.getDefinition(), "calResultMap")) {
            Field field = ReflectUtil.getField(convert.getDefinition(), "calResultMap");
            try {
                ReflectUtil.setFieldValue(calUnitBean.getValue(), field, resultEntityMap);
            } catch (Exception e) {
                // ignore
                log.warn("尝试着按约定对预定义了类型为 CalResultEntity 的Map，及类型为Map<Serializable, CalResultEntity>，名称为calResultMap 的字段设置结果集失败，可能是该字段不存在或者不符合约定，或者类型不匹配");
            }
        }
    }

    private Collection<ParameterEntity> createParams(CalUnitBeanDefinition<T> calUnitBean, Field field) {
        Collection<ParameterEntity> params = new ArrayList<>();
        calUnitBean.getParameters().forEach((k, v) -> {
            params.add(ParameterEntity.builder()
                    .id(k.toString())
                    .value(ReflectUtil.getFieldValue(v.getValue(), field.getName()))
                    .build());
        });
        return params;
    }

    private void initialize() {
        formulaCalEngine = new SpELFormulaCalEngine<T>();
        convert = new AnnotationFormulaDefinitionConvert<>(definition);
        convert.setResolver(formulaCalEngine.getResolver());
        dataSources.setConverter(convert);
    }
}
